/*
 * Schema.java
 *
 * Created on 19 marzo 2004, 10.52
 */

package net.ermannofranco.xml.schema;

import net.ermannofranco.xml.AbstractTag;
import net.ermannofranco.xml.Doc;
import net.ermannofranco.xml.Namespace;
import net.ermannofranco.xml.Static;
import net.ermannofranco.xml.Tag;
import net.ermannofranco.xml.schema.validation.ErrorListener;
import net.ermannofranco.xml.schema.validation.XsdValidator;

/**
 * Rappresenta un XMLSchema; e' istanziabile direttamente. Fornisce in
 * automatico il namespace canonico, il prefisso 'xsd', l'estensione del file
 * .xsd
 * 
 * @author ermanno (ermanno.franco@gmail.com)
 */
public class Schema extends Doc implements ISchema {

	/**
	 * 
	 */
	private static final long serialVersionUID = -4973404452817329605L;
	private static final String DEFAULTXMLSCHEMAURI = "http://www.w3.org/2001/XMLSchema";
	private final String schemaPrefix;
	private String formPrefix;
	private static String DEFAULTID = "xsd";
	private static String DEFAULT_EXT = "." + DEFAULTID;
	public static final Namespace XML_NS = new Namespace("xml",
			"http://www.w3.org/XML/1998/namespace");
	private static final Namespace DEFAULT_NS = new Namespace(DEFAULTID,
			DEFAULTXMLSCHEMAURI);
	private XsdValidator validator;
	private boolean validated;

	/** Creates a new instance of Schema */
	public Schema() {
		this(DEFAULT_NS, null);
	}

	public Schema(double version) {

		this();
		setSchemaVersion(version);
	}

	public Schema(String schemaPrefix) {
		this(schemaPrefix, null);
	}

	public Schema(String schemaPrefix, double version) {

		this(schemaPrefix);
		setSchemaVersion(version);
	}

	/**
	 * Creates a new instance of Schema
	 * 
	 * @param creator
	 *            classe usata dal metodo save() come nome di file
	 */
	public Schema(Class<?> creator) {
		this(DEFAULT_NS, creator);
	}

	public Schema(Class<?> creator, double version) {

		this(creator);
		setSchemaVersion(version);
	}

	/**
	 * Se si vuole usare un prefisso personalizzato...
	 * 
	 * @param schemaPrefix
	 *            prefisso
	 * @param creator
	 *            classe usata dal metodo save() come nome di file
	 */
	public Schema(String schemaPrefix, Class<?> creator) {
		this(new Namespace(schemaPrefix, DEFAULTXMLSCHEMAURI), creator);
	}

	public Schema(String schemaPrefix, Class<?> creator, double version)
			throws Exception {

		this(schemaPrefix, creator);
		setSchemaVersion(version);
	}

	/**
	 * Per ora sconsigliato, questo costruttore potrebbe sevire nel caso
	 * cambiasse l'uri del w3c relativo a XMLSchema
	 * 
	 * @param schemaPrefix
	 *            prefisso
	 * @param schemaURI
	 *            uri di XMLSchema
	 * @param creator
	 *            classe usata dal metodo save() come nome di file
	 */
	public Schema(Namespace ns, Class<?> creator) {

		super("schema", ns, creator);
		validator = new XsdValidator();
		schemaPrefix = ns.getPrefix();
		extension = DEFAULT_EXT;
		if (Static.isMinimalStr(schemaPrefix)) {
			throw new SchemaException(
					"prefisso di namespace schema obbligatorio");
		}

		setEncoding("UTF-8");
		omitStandaloneAttr();
		addNS(XML_NS);
	}

	public Schema(Namespace ns, Class<?> creator, double version) {

		this(ns, creator);
		setSchemaVersion(version);
	}

	/**
	 * specifica se i nomi dei tag nel documento vanno sempre indicati completi
	 * di namespace
	 * 
	 * @param flag
	 *            indicatore booleano
	 * @return autoref
	 */
	public Schema setElemFormDefQualified(boolean flag) {
		addAttr("elementFormDefault", flag ? FormValues
				.getValue(FormValues.QUAL) : FormValues
				.getValue(FormValues.UNQUAL));
		return this;
	}

	/**
	 * Schema version
	 * 
	 * @param d
	 * @return
	 */
	public Schema setSchemaVersion(double d) {
		addAttr("version", String.valueOf(d));
		return this;
	}

	/**
	 * specifica se i nomi degli attributi nel documento vanno sempre indicati
	 * completi di namespace
	 * 
	 * @param flag
	 *            indicatore booleano
	 * @return autoref
	 */
	public Schema setAttrFormDefQualified(boolean flag) {
		addAttr("attributeFormDefault", flag ? FormValues
				.getValue(FormValues.QUAL) : FormValues
				.getValue(FormValues.UNQUAL));
		return this;
	}

	/**
	 * indica il namespace del documento
	 * 
	 * @param uri
	 *            uri del namespace
	 * @return autoref
	 */
	/*
	 * public Schema setXmlns(String uri) { addAttr("xmlns", uri); return this;
	 * }
	 */

	/**
	 * restituisce il prefisso, oppure "xsd" per default
	 * 
	 * @return prefisso dello schema
	 */
	public String getSchemaPrefix() {
		return schemaPrefix;
	}

	/**
	 * genera un oggetto SimpleType
	 * 
	 * @param name
	 *            nome del tag, obbligatorio a primo livello, cio� in Schema
	 * @return ref all'oggetto creato
	 */
	public SimpleType addSimpleType(String name) {
		SimpleType att = new SimpleType(1, this);
		att.setName(name);
		add(att);// vado ad aggiungerlo all'ArrayList della superclasse Tag
		return att;
	}

	/**
	 * genera un oggetto ComplexType
	 * 
	 * @param name
	 *            nome del tag, obbligatorio a primo livello, cioe' in Schema
	 * @return ref all'oggetto creato
	 */
	public ComplexType addComplexType(String name) {
		ComplexType ct;
		add(ct = new ComplexType(1, this).setName(name));// vado ad aggiungerlo
		// all'ArrayList
		// della superclasse
		// Tag
		return ct;
	}

	/**
	 * genera un oggetto Import
	 * 
	 * @param namespace
	 *            nome del namespace
	 * @param schemaURI
	 *            uri di riferimento
	 * @return ref all'oggetto creato
	 */
	public Import addImport(String namespace, String schemaURI) {
		Import im = new Import(this);
		im.setNamespace(namespace).setSchemaLocation(schemaURI);
		add(im);// vado ad aggiungerlo all'ArrayList della superclasse Tag
		return im;
	}

	public Import addImport(String namespace) {
		Import im = new Import(this);
		im.setNamespace(namespace);
		add(im);// vado ad aggiungerlo all'ArrayList della superclasse Tag
		return im;
	}

	/**
	 * genera un oggetto Include
	 * 
	 * @param schemaURI
	 *            uri di riferimento
	 * @return ref all'oggetto creato
	 */
	public Include addInclude(String schemaURI) {
		Include ct = new Include(this);
		ct.setSchemaLocation(schemaURI);
		add(ct);// vado ad aggiungerlo all'ArrayList della superclasse Tag
		return ct;
	}

	/**
	 * genera un oggetto Redefine
	 * 
	 * @param schemaURI
	 *            uri di riferimento
	 * @return ref all'oggetto creato
	 */
	public Redefine addRedefine(String schemaURI) {
		Redefine re = new Redefine(this);
		re.setSchemaLocation(schemaURI);
		add(re);// vado ad aggiungerlo all'ArrayList della superclasse Tag
		return re;
	}

	/**
	 * genera un oggetto Attribute
	 * 
	 * @return ref all'oggetto creato
	 */
	// public Attribute addAttributeTag() {
	// Attribute att = new Attribute(1, this);
	// add(att);// vado ad aggiungerlo all'ArrayList della superclasse Tag
	// return att;
	// }

	/**
	 * genera un oggetto Attribute
	 * 
	 * @param name
	 *            nome attributo
	 * @return ref all'oggetto creato
	 */
	public Attribute addTagAttribute(String name) {
		Attribute att = new Attribute(1, this);
		att.setName(name);
		add(att);// vado ad aggiungerlo all'ArrayList della superclasse Tag
		return att;
	}

	/**
	 * add a "Attribute" tag with given name and type.
	 * 
	 * @return Attribute
	 */
	public Attribute addTagAttribute(String name, XmlType type) {
		Attribute ret = addTagAttribute(name);
		ret.setType(type);
		return ret;
	}

	/**
	 * add a "Attribute" tag with given name and type reference.
	 * 
	 * @return Attribute
	 */
	public Attribute addTagAttribute(String name, ContentType typeRef) {
		Attribute ret = addTagAttribute(name);
		ret.setType(typeRef);
		return ret;
	}

	/**
	 * add a "Attribute" tag with given name and type name.
	 * 
	 * @return Attribute
	 */
	public Attribute addTagAttribute(String name, String typeName) {
		Attribute ret = addTagAttribute(name);
		ret.setType(typeName);
		return ret;
	}

	/**
	 * genera un oggetto AttributeGroup
	 * 
	 * @return ref all'oggetto creato
	 */
	public AttributeGroup addAttributeGroup(String name) {
		AttributeGroup att;
		add(att = new AttributeGroup(1, this));
		att.setName(name);
		return att;
	}

	public Group addGroup(String name) {
		Group att;
		add(att = new Group(1, this));
		att.setName(name);
		return att;
	}

	private Annotation addAnnotation() {
		Annotation a = new Annotation(1, this);
		add(a, true);
		return a;
	}

	/**
	 * aggiunge un tag Annotation che contiene un tag Documentation
	 * 
	 * @param explanation
	 *            Stringa di documentazione
	 * @return autoriferimento all'oggetto corrente
	 */
	public Schema addDocumentation(String explanation) {
		addAnnotation().addDocumentation(explanation);
		return this;
	}

	/**
	 * aggiunge un tag Annotation che contiene un tag Documentation
	 * 
	 * @param explanation
	 *            testo della documentazione
	 * @param language
	 *            attributo di xml:lang, seguire le regole di i18n
	 * @return riferimento all'oggetto corrente
	 */
	public Schema addDocumentation(String explanation, String language) {
		addAnnotation().addDocumentation(explanation, language);
		return this;
	}

	/**
	 * indica il namespace a cui faranno riferimento i documenti da convalidare
	 * 
	 * @param URIref
	 *            uri di riferimento
	 * @return autoref
	 */
	public Schema setTargetNamespace(Namespace ns) {
		addAttr("targetNamespace", ns.getUri());
		addNS(ns);
		setFormPrefix(ns.getPrefix());
		return this;
	}

	private void setFormPrefix(String prefix) {
		this.formPrefix = prefix;

	}

	public Schema setTargetNamespace(String prefix, String uri) {
		return setTargetNamespace(new Namespace(prefix, uri));
	}

	public Schema setTargetNamespace(String uri) {
		return setTargetNamespace(new Namespace("", uri));
	}

	/**
	 * imposta il final default
	 * 
	 * @param finalDefault
	 *            final default
	 * @return autoref
	 */
	public Schema setFinalDefault(String finalDefault) {
		addAttr("finalDefault", finalDefault);// cdata
		return this;
	}

	/**
	 * imposta il block default
	 * 
	 * @param blockDefault
	 *            il block default
	 * @return autoref
	 */
	public Schema setBlockDefault(String blockDefault) {
		addAttr("blockDefault", blockDefault);// cdata
		return this;
	}

	/**
	 * genera un oggetto Notation
	 * 
	 * @param name
	 *            nome della NOTATION
	 * @param publicName
	 *            nome pubblico
	 * @return ref all'oggetto creato
	 */
	public Notation addNotation(String name, String publicName) {
		Notation a;
		add(a = new Notation(1, this, name, publicName));
		return a;

	}

	/**
	 * fissa un id dello Schema. Non so se serve...
	 * 
	 * @param ID
	 *            id, NCNAME
	 * @return autoref
	 */
	public ISchemaTag setId(String ID) {
		Static.validateID(ID);

		addAttr("id", ID);
		return this;

	}

	/**
	 * indica l'attributo di linguaggio
	 * 
	 * @param language
	 *            linguaggio secondo le norme i18n
	 * @return autoref
	 */
	public Schema setLanguage(String language) {
		addAttr("xml:lang", language);
		return this;
	}

	/**
	 * restituisce ".xsd"
	 * 
	 * @return .xsd
	 */
	// public String getDefaultExtension() {
	// return "." + DEFAULTID;
	// }

	/**
	 * genera un oggetto Element
	 * 
	 * @param name
	 *            nome del tag, obbligatorio al primo livello
	 * @return ref all'oggetto creato
	 */
	public Element addElem(String name) {
		Element e;
		add(e = new Element(1, this).setName(name));
		return e;

	}

	public Element addElem(String name, ContentType typeRef) {
		Element e = addElem(name);
		e.setType(typeRef);
		return e;

	}

	public Element addElem(String name, XmlType type) {
		Element e = addElem(name);
		e.setType(type);
		return e;

	}

	// rifletterci sopra
	/**
	 * sovrascritto, genera eccezione
	 * 
	 * @param nome
	 *            non usare perche' genera eccezione
	 * @return niente perche' genera eccezione
	 */
	public Tag addTag(String nome) {
		throw new SchemaException("Proibito usare Tag personalizzati in Schema");
	}

	/**
	 * sovrascritto , genera eccezione
	 * 
	 * @param testo
	 *            non usare perche' genera eccezione
	 * @return niente perche' genera eccezione
	 */
	public final AbstractTag addTextBlock(String testo) {
		throw new SchemaException(
				"Proibito inserire testo personalizzato in Schema");
	}

	/**
	 * sovrascritto, genera eccezione
	 * 
	 * @param nome
	 *            non usare perche' genera eccezione
	 * @return niente perche' genera eccezione
	 */
	public final String removeAttribute(String nome) {
		throw new SchemaException("Proibito rimuovere attributi in Schema");
	}

	/**
	 * sovrascritto da Doc, genera eccezione
	 * 
	 * @param location
	 *            non usare perche' genera eccezione
	 * @return niente perche' genera eccezione
	 */
	public final Doc setNoNSSchemaLocation(String location) {
		throw new SchemaException(
				"Proibito usare setNoNSSchemaLocation() in Schema");
	}

	/**
	 * sovrascritto da Doc, genera eccezione
	 * 
	 * @return niente perche' genera eccezione
	 */
	public final Doc addCanonicNS() {
		throw new SchemaException("Proibito usare addCanonicNS() in Schema");
	}

	/**
	 * sovrascritto da Doc, genera eccezione
	 * 
	 * @param name
	 *            non usare perche' genera eccezione
	 * @param uri
	 *            non usare perche' genera eccezione
	 * @return niente perche' genera eccezione
	 */
	public final Doc setPublicDoctype(String name, String uri) {
		throw new SchemaException("Proibito usare setPublicDoctype() in Schema");
	}

	/**
	 * sovrascritto da Doc, genera eccezione
	 * 
	 * @param uri
	 *            non usare perche' genera eccezione
	 * @return niente perche' genera eccezione
	 */
	public final Doc setSystemDoctype(String uri) {
		throw new SchemaException("Proibito usare setSystemDoctype() in Schema");
	}

	public Doc setSchemaLocation(String location) {
		throw new SchemaException(
				"Proibito usare setSchemaLocation() in Schema");
	}

	public String getFormPrefix() {
		return formPrefix;
	}

	public Schema validate() {
		validator.validate(this);
		validated = true;
		return this;
	}

	public int getErrors() {
		if (!validated) {
			validate();
		}
		return validator.getErrors();
	}

	public Schema addErrorListener(ErrorListener el) {
		validator.addErrorListener(el);
		return this;
	}

	public Schema removeErrorListener(ErrorListener el) {
		validator.removeErrorListener(el);
		return this;
	}

	public Schema unregisterDefaultErrorListener() {
		validator.unregisterDefaultErrorListener();
		return this;
	}

}
